#include "mem_chunk.h"
#include "mem.h"
#include <stdlib.h>
#include <string.h>

#define MEM_ALIGN sizeof (void *)
#define MEM_AREA_SIZE sizeof (void *)

typedef struct z_mem_area z_mem_area;

/*
 *@internal
 *@breif a mem chunk
 */
struct z_mem_chunk
{
  int num_mem_areas;            /* the number of memory areas */
  int num_marked_areas;         /* the number of areas marked for deletion */
  size_t atom_size;             /* the size of an atom */
  size_t area_size;             /* the size of a memory area */
  size_t rarea_size;            /* the size of a real memory area */
  z_mem_area *mem_area;         /* the current memory area */
  z_mem_area *mem_areas;        /* a list of all the mem areas owned by this chunk */
};

/*
 * @internal
 * @brief a mem area
 */
struct z_mem_area
{
  z_mem_area *next;             /* the next mem area */
  z_mem_area *prev;             /* the previous area */
  size_t index;                 /* the current index into the "mem" array */
  size_t free;                  /* the number of free bytes in this mem area*/
  size_t allocated;             /*the number of atoms allocated from this area */ 

  char mem[MEM_AREA_SIZE];      /* the mem array from which atoms */
                                /* get allocated the actual size */
                                 /* of this array is determined
                                 * by the mem chunk "area_size"
                                 */
};

static size_t internal_z_mem_chunk_compute_size(size_t size, size_t min_size) __attribute__ ((nonnull));

/* Makes a new Memory-Chunk Allocator
 * @param atom_size size of each piece
 * @param area_size size of each allocated chunk
 */

z_mem_chunk* z_mem_chunk_new(uint32_t atom_size, uint32_t area_size) 
{
  z_mem_chunk *mem_chunk;
  if (area_size < atom_size)
    return NULL;
  area_size = (area_size + atom_size - 1) / atom_size;
  area_size *= atom_size;

  mem_chunk = z_new(z_mem_chunk, 1);
  mem_chunk->num_mem_areas = 0;
  mem_chunk->num_marked_areas = 0;
  mem_chunk->mem_area = NULL;
  mem_chunk->mem_areas = NULL;
  mem_chunk->atom_size = atom_size;

  if (mem_chunk->atom_size % MEM_ALIGN)
    mem_chunk->atom_size += MEM_ALIGN - (mem_chunk->atom_size % MEM_ALIGN);

  mem_chunk->rarea_size = internal_z_mem_chunk_compute_size(area_size + sizeof(z_mem_area) - MEM_AREA_SIZE, atom_size + sizeof(z_mem_area) - MEM_AREA_SIZE);

  mem_chunk->area_size = mem_chunk->rarea_size - (sizeof(z_mem_area)-(MEM_AREA_SIZE));
  return mem_chunk;
  
}
/* allocate one piece mem insight the chunk.
 * @param mem_chunk 
 */
void* z_mem_chunk_alloc(z_mem_chunk* mem_chunk)
{
  void *mem;
  
  /* no chunk or run out of size insight, so allocate new */
  if ((!mem_chunk->mem_area)||((mem_chunk->mem_area->index+mem_chunk->atom_size) > mem_chunk->area_size))
  {
    mem_chunk->mem_area = zmalloc(mem_chunk->rarea_size);
    mem_chunk->num_mem_areas += 1;
    mem_chunk->mem_area->next = mem_chunk->mem_areas; /* the linked list head */
    mem_chunk->mem_area->prev = NULL;                           /* insert in the front */
    
    if (mem_chunk->mem_areas)
      mem_chunk->mem_areas->prev = mem_chunk->mem_area;
    mem_chunk->mem_areas = mem_chunk->mem_area;
    
    mem_chunk->mem_area->index = 0;
    mem_chunk->mem_area->free = mem_chunk->area_size;
    mem_chunk->mem_area->allocated = 0;
  }
  
  mem = &mem_chunk->mem_area->mem[mem_chunk->mem_area->index];
  mem_chunk->mem_area->index += mem_chunk->atom_size;
  mem_chunk->mem_area->free -= mem_chunk->atom_size;
  mem_chunk->mem_area->allocated += 1;

  return mem;
}
/* allocate a cleared piece
 * @param mem_chunk a z_mem_chunk
 * @return memory
 */
void* z_mem_chunk_alloc0(z_mem_chunk* mem_chunk)
{
  void *mem;
  mem = z_mem_chunk_alloc(mem_chunk);
  if (mem)
    memset(mem, 0, mem_chunk->atom_size);
  return mem;
}

void z_mem_chunk_destroy(z_mem_chunk* mem_chunk)
{
  z_mem_area *mem_areas, *temp_area;
  mem_areas = mem_chunk->mem_areas;
  while (mem_areas) {
    temp_area = mem_areas;
    mem_areas = mem_areas->next;
    zfree(temp_area);
  }
  zfree(mem_chunk);
}

size_t z_mem_chunk_size(z_mem_chunk* mem_chunk)
{
  z_mem_area *mem_area;
  size_t size = 0;
  
  for (mem_area = mem_chunk->mem_areas; mem_area; mem_area=mem_area->next)
  {
    size += mem_chunk->atom_size * mem_area->allocated;
  }
  return size;
}

static size_t internal_z_mem_chunk_compute_size(size_t size, size_t min_size) 
{
  
  size_t power_of_2;
  size_t lower, upper;

  power_of_2 = 16;
  while (power_of_2 < size)
    power_of_2 <<= 1;

  lower = power_of_2 >> 1;
  upper = power_of_2;

  if (size - lower < upper - size && lower >= min_size)
    return lower;
  else
    return upper;
}

#ifdef DEBUG
#include <stdio.h>

int main() 
{
  int N = 1020;
  
  z_mem_chunk *p = z_mem_chunk_new(sizeof(char), 4096);
  
  printf("num areas:%d\tatom size %d\tarea size %d\trarea size %d\n", p->num_mem_areas,p->atom_size, p->area_size, p->rarea_size);
 
 while (N--) {
    
  z_mem_chunk_alloc0(p);
  }
  printf("num areas:%d\tatom size %d\tarea size %d\trarea size %d\n", p->num_mem_areas,p->atom_size, p->area_size, p->rarea_size);

  z_mem_chunk_destroy(p);
  
  return 0;
}

#endif
